/*
    Android Asynchronous Http Client
    Copyright (c) 2013 JIXIAOLONG <MicroJixl@gmail.com>

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
 */
package com.jxl.netframe;

import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.LinkedList;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;

import android.os.Process;

import com.jxl.netframe.apachebase.ApacheHttpClient;
import com.jxl.netframe.log.Logger;

/** 
 * @author JiXL
 * @time 2013-6-6 下午2:05:19
 */
public class QueueTask implements Runnable{
	private final String TAG = getClass().getName();

	//reference
	private ApacheHttpClient client;
	private LinkedList<RequestParameter> requestQueue = null;
	private ProcessListener processListener;

	private int executionCount = 0;
	private RequestParameter currentProcess = null;
	private boolean running = true;
	private boolean cancel = false;
	private byte[] QMX = null;
	private Thread background = null;
	private int recoverTime = 0;
	private int MAX_RECOVER_TIME = 10;

	public QueueTask(ApacheHttpClient client,LinkedList<RequestParameter> requestQueue,ProcessListener processListener) {
		this.client = client;
		this.requestQueue = requestQueue;
		this.processListener = processListener;
	}

	private void init(){
		running = true;
		cancel = false;
		QMX = new byte[0];
		executionCount = 0;
	}

	public void start(){
		if(recoverTime > MAX_RECOVER_TIME) {
			Logger.d(TAG, "Queue Task restart max time...");
			return;	
		}
		recoverTime ++;
		Logger.d(TAG, "Queue Task start...");
		init();
		if(background != null){
			background.interrupt();
		}
		background = new Thread(this);
		background.start();
	}
	
	public void shutDown(){
		running = false;
		cancel();
		process();
	}

	public RequestParameter getCurrentProcess() {
		return currentProcess;
	}

	public void process(){
		synchronized (QMX) {
			QMX.notify();
		}
	}

	@Override
	public void run() {
		try {
			Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
			while (running) {
				cancel = false;
				if((currentProcess=requestQueue.poll()) == null){
					try {
						currentProcess = null;
						synchronized (QMX) {
							QMX.wait();
						}
					} catch (InterruptedException e) {
						Logger.d(TAG,"current thread interrupt.");
						break;
					}
				}
				if(currentProcess != null && !cancel){
					try {
						makeRequestWithRetries(currentProcess);
					} catch (IOException e) {
						Logger.d(TAG, "retry failed.");
						if(processListener != null){
							processListener.failed(currentProcess, e);
						}
						continue;
					}
				}
			}
			Logger.d(TAG, "queue task stop.");
		} catch (Exception e) {
			if(processListener != null){
				processListener.failed(currentProcess, e);
			}
			Logger.e(TAG, "uncatch exception"+e.getMessage()+"\n\t restart Task...");
			start();
		}
	}

	private void makeRequestWithRetries(RequestParameter rq) throws ConnectException {
		// This is an additional layer of retry logic lifted from droid-fu
		// See: https://github.com/kaeppler/droid-fu/blob/master/src/main/java/com/github/droidfu/http/BetterHttpRequestBase.java
		boolean retry = true;
		IOException cause = null;
		while (retry) {
			try {
				if(cancel){
					return;
				}
				makeRequest(rq);
				return;
			} catch (UnknownHostException e) {
				Logger.i(TAG, e.getMessage());
				if(processListener != null && !cancel) {
					processListener.failed(rq,e);
				}
				return;
			}catch (SocketException e){
				Logger.i(TAG, e.getMessage());
				// Added to detect host unreachable
				if(processListener != null && !cancel) {
					processListener.failed(rq,e);
				}
				return;
			}catch (SocketTimeoutException e){
				Logger.i(TAG, e.getMessage());
				if(processListener != null && !cancel) {
					processListener.failed(rq,e);
				}
				return;
			} catch (IOException e) {
				Logger.i(TAG, e.getMessage());
				if(cancel){
					return;
				}
				cause = e;
				retry = client.retryRequest(cause, ++executionCount);
				if(processListener != null) {
					processListener.retry(rq, executionCount, e);
				}
			} catch (NullPointerException e) {
				Logger.i(TAG, e.getMessage());
				if(cancel){
					return;
				}
				// there's a bug in HttpClient 4.0.x that on some occasions causes
				// DefaultRequestExecutor to throw an NPE, see
				// http://code.google.com/p/android/issues/detail?id=5255
				cause = new IOException("NPE in HttpClient" + e.getMessage());
				retry = client.retryRequest(cause, ++executionCount);
				if(processListener != null) {
					processListener.retry(rq, executionCount, e);
				}
			}
		}

		// no retries left, crap out with exception
		ConnectException ex = new ConnectException();
		ex.initCause(cause);
		throw ex;
	}

	private void makeRequest(RequestParameter rq) throws IOException {
		if(!Thread.currentThread().isInterrupted() && !cancel) {
			try {
				if(processListener != null)rq.setIOListener(processListener);
				HttpResponse response = client.sendRequest(rq);
				if(!Thread.currentThread().isInterrupted() && !cancel) {
					processResponse(response,rq);
				} else{
					//TODO: should raise InterruptedException? this block is reached whenever the request is cancelled before its response is received
				}
			} catch (IOException e) {
				if(!Thread.currentThread().isInterrupted() && !cancel) {
					throw e;
				}
			}
		}
	}

	private void processResponse(HttpResponse response,RequestParameter rq){
		StatusLine status = response.getStatusLine();
		String responseBody = null;
		HttpEntity entity = null;
		HttpEntity temp = null;
		try {
			temp = response.getEntity();
			if(temp != null) {
				entity = new BufferedHttpEntity(temp);
				responseBody = EntityUtils.toString(entity, HTTP.UTF_8);
			}
			Logger.d(TAG,"http response string["+responseBody+"]");
		} catch(IOException e) {
			if(processListener != null && !cancel && !Thread.currentThread().isInterrupted()) {
				processListener.failed(rq, e);
			}
			return;
		} finally{
			if(temp != null){
				try {
					temp.consumeContent();
				} catch (IOException e) {
					if(processListener != null && !cancel && !Thread.currentThread().isInterrupted()) {
						processListener.failed(rq, e);
					}
				}
			}
		}
		if(status.getStatusCode() >= 300) {
			if(processListener != null && !cancel && !Thread.currentThread().isInterrupted()) {
				processListener.failed(rq, new Exception("服务器错误"));
			}
		} else {
			if(processListener != null && !cancel && !Thread.currentThread().isInterrupted()) {
				processListener.finished(rq, responseBody);
			}
		}
	}

	public boolean cancel(int id) {
		if(currentProcess.getId() == id){
			cancel();
			//don't return cancel because thread not safety. 
			return true;
		}
		return false;
	}
	
	public void cancel(){
		cancel = true;
	}
}
